#include <algorithm>
#include <iostream>
#include <WiFi.h>
#include <HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <Button2.h>            //https://github.com/LennartHennigs/Button2
#include <BH1750.h>             //https://github.com/claws/BH1750
#include <OneWire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_SHT31.h>
#include <Adafruit_NeoPixel.h>
#include "DHT.h"
#include "configuration.h"

Button2       button(BOOT_PIN);
Button2       useButton(USER_BUTTON);
Ticker        delayTimer;
BH1750              lightMeter(OB_BH1750_ADDRESS);  //0x23
DHT                 dht(DHT1x_PIN, DHTTYPE);
OneWire             ds;
Adafruit_SHT31      sht31 = Adafruit_SHT31(&Wire1);
Adafruit_BME280     bme;                            //0x77
WiFiClient TCPclient;
Adafruit_NeoPixel *pixels = NULL;

char config_flag = 0;   //配网成功标识
//tcp客户端相关初始化
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;//心跳
unsigned long preTCPStartTick = 0;//连接
bool preTCPConnected = false;

uint64_t                timestamp       = 0;
uint8_t                 ds18b20Addr[8];
uint8_t                 ds18b20Type;
float data1 = 0;    //温度
float data2 = 0;    //湿度
uint8_t data3 = 0;  //土壤湿度
float data4 = 0;    //光照度
uint8_t data5 = 0;  //盐度
int count=0;
uint8_t wifi_flag = 0;

bool                    has_lora_shield = false;
bool                    has_bmeSensor   = false;
bool                    has_lightSensor = false;
bool                    has_dhtSensor   = false;
bool                    has_sht3xSensor = false;
bool                    has_ds18b20     = false;
bool                    has_dht11       = false;

struct config_type    //用于存储配网信息
{
  char stassid[32];
  char stapsw[64];
  char cuid[40];
  char ctopic[32];
  uint8_t reboot;
  uint8_t magic;
};
config_type config;

typedef enum {
  BME280_SENSOR_ID,
  DHTxx_SENSOR_ID,
  SHT3x_SENSOR_ID,
  BHT1750_SENSOR_ID,
  SOIL_SENSOR_ID,
  SALT_SENSOR_ID,
  DS18B20_SENSOR_ID,
  VOLTAGE_SENSOR_ID,
}sensor_id_t;

typedef struct {
  uint32_t timestamp;     /**< time is in milliseconds */
  float temperature;      /**< temperature is in degrees centigrade (Celsius) */
  float light;            /**< light in SI lux units */
  float pressure;         /**< pressure in hectopascal (hPa) */
  float humidity;         /**<  humidity in percent */
  float altitude;         /**<  altitude in m */
  float voltage;           /**< voltage in volts (V) */
  uint8_t soli;           //Percentage of soil
  uint8_t salt;           //Percentage of salt
}higrow_sensors_event_t;

void delayRestart(float t) 
{
  delayTimer.attach(t, []() 
  {
    ESP.restart();
  });
}

void saveConfig()   //存储配网信息
{
  int rand_key;
  uint8_t mac[6];
  WiFi.macAddress(mac);
  config.reboot = 0;
  EEPROM.begin(512);
  uint8_t *p = (uint8_t*)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}
 
void doSmartconfig()
{
  Serial.print(" Smartconfig begin,Waiting for WeChat Config.....");
  WiFi.mode(WIFI_STA);
  WiFi.stopSmartConfig();
  WiFi.beginSmartConfig();
  int cnt = 0;
  bool flag_ok = false;
  while (!WiFi.smartConfigDone()) 
  {
    delay(300);
    if (flag_ok == true) continue;
    if (WiFi.smartConfigDone()) 
    {
        strcpy(config.stassid, WiFi.SSID().c_str());
        strcpy(config.stapsw, WiFi.psk().c_str());
        config.magic = MAGIC_NUMBER;
        saveConfig();
        flag_ok = true;
    }
    cnt++;
    if (cnt >= 600) 
    {
      delayRestart(0);
    }
  }
}

void callConfig(){
    String mac = WiFi.macAddress().substring(8);//取mac地址做主题用
    mac.replace(":", "");//去掉:号
    WiFiClient client_bemfa_WiFiClient;
    HTTPClient http_bemfa_HTTPClient;
    http_bemfa_HTTPClient.begin(client_bemfa_WiFiClient,"http://pro.bemfa.com/vv/setSmartConfig?version=1&user="+mac);
    int httpCode = http_bemfa_HTTPClient.GET();
    if (httpCode == 200) 
    {
      Serial.println();
      Serial.println("wifi smartconfig ok");
    }
    http_bemfa_HTTPClient.end();
}

void initWiFi()   //初始化wifi信息，并连接路由器网络
{
  char temp[32];
  uint8_t mac[6];
  WiFi.macAddress(mac);
  sprintf(temp, "%s_%02X%02X%02X", HOST_NAME, mac[3], mac[4], mac[5]);
  WiFi.hostname(temp);
  if(WiFi.status() != WL_CONNECTED)
  {
    WiFi.disconnect();//断开连接
    WiFi.mode(WIFI_STA);//STA模式
    WiFi.begin(config.stassid, config.stapsw);//连接路由器
  }
  while (WiFi.status() != WL_CONNECTED) 
  {//检查是否连接成功
    delay(500);
    count++;
    Serial.print(".");
    if(count == 60)
    {
      wifi_flag = 1;
      Serial.println("wifi connected Failure.");
      break;
    }
    else
      wifi_flag = 0;
  }
  if(config_flag == 1)
  {
    callConfig();
  }
  if(wifi_flag == 0)
  {
    Serial.println("wifi is connected.");
    Serial.print("ssid:");
    Serial.println(WiFi.SSID());
    Serial.print("psw:");
    Serial.println(WiFi.psk());
    WiFi.softAP(temp);
  }
  
}

uint8_t *p = (uint8_t*)(&config);
void loadConfig()   //加载存储的信息
{
    uint8_t mac[6];
    WiFi.macAddress(mac);
    EEPROM.begin(512);
    for (int i = 0; i < sizeof(config); i++)
    {
      *(p + i) = EEPROM.read(i);
    }
    if(config.magic != 0xAA)
    {
      config_flag = 1;
    }
    EEPROM.begin(512);
    for (int i = 0; i < sizeof(config); i++)
    {
      EEPROM.write(i, *(p + i));
    }
    EEPROM.commit();
    delay(2000);
    EEPROM.begin(512);
    config.reboot = 0;
    for (int i = 0; i < sizeof(config); i++)
    {
      EEPROM.write(i, *(p + i));
    }
    EEPROM.commit();
    delay(2000);
}

void waitKey()
{
  if(config_flag == 1){
      doSmartconfig();
    }
}

void restoreFactory(Button2 &b)   //恢复出厂设置，清除存储的wifi信息
{
    Serial.println("Restore Factory....... ");
    config.magic = 0x00;
    strcpy(config.stassid, "");
    strcpy(config.stapsw, "");
    config.magic = 0x00;
    saveConfig();
    delayRestart(1);
    while (1) 
    {
      delay(100);
    }
 }

void sleepHandler(Button2 &b)
{
    Serial.println("Enter Deepsleep ...");
    esp_sleep_enable_ext1_wakeup(GPIO_SEL_35, ESP_EXT1_WAKEUP_ALL_LOW);
    delay(1000);
    esp_deep_sleep_start();
}

float getDsTemperature(void)
{
    uint8_t data[9];
    uint8_t present;
    float celsius, fahrenheit;
    ds.reset();
    ds.select(ds18b20Addr);
    ds.write(0x44, 1);        // start conversion, with parasite power on at the end
    delay(1000);     // maybe 750ms is enough, maybe not
    // we might do a ds.depower() here, but the reset will take care of it.
    present = ds.reset();
    ds.select(ds18b20Addr);
    ds.write(0xBE);         // Read Scratchpad
    for (int i = 0; i < 9; i++)
    {           // we need 9 bytes
        data[i] = ds.read();
    }
    int16_t raw = (data[1] << 8) | data[0];
    if (ds18b20Type) 
    {
        raw = raw << 3; // 9 bit resolution default
        if (data[7] == 0x10) 
        {
            // "count remain" gives full 12 bit resolution
            raw = (raw & 0xFFF0) + 12 - data[6];
        }
    } 
    else 
    {
        byte cfg = (data[4] & 0x60);
        // at lower res, the low bits are undefined, so let's zero them
        if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
        else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
        else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    }
    celsius = (float)raw / 16.0;
    return celsius;
}

bool get_higrow_sensors_event(sensor_id_t id, higrow_sensors_event_t &val)
{
    switch (id) 
    {
    case BME280_SENSOR_ID: 
    {
        val.temperature = bme.readTemperature();
        val.humidity = (bme.readPressure() / 100.0F);
        val.altitude = bme.readAltitude(1013.25);
    }
    break;

    case SHT3x_SENSOR_ID: 
    {
        float t = sht31.readTemperature();
        float h = sht31.readHumidity();
        if (! isnan(t)) {  // check if 'is not a number'
            Serial.print("Temp *C = "); Serial.print(t); Serial.print("\t\t");
        } else {
            Serial.println("Failed to read temperature");
        }
        if (! isnan(h)) {  // check if 'is not a number'
            Serial.print("Hum. % = "); Serial.println(h);
        } else {
            Serial.println("Failed to read humidity");
        }
        val.temperature = t;
        val.humidity = h;
    }
    break;

    case DHTxx_SENSOR_ID: 
    {
        val.temperature = dht.readTemperature();
        val.humidity = dht.readHumidity();
        if (isnan(val.temperature)) {
            val.temperature = 0.0;
        }
        if (isnan(val.humidity)) {
            val.humidity = 0.0;
        }
    }
    break;

    case BHT1750_SENSOR_ID: 
    {
        val.light = lightMeter.readLightLevel();
        if (isnan(val.light)) {
            val.light = 0.0;
        }
    }
    break;

    case SOIL_SENSOR_ID: 
    {
        uint16_t soil = analogRead(SOIL_PIN);
        val.soli = map(soil, 0, 4095, 100, 0);
    }
    break;

    case SALT_SENSOR_ID: 
    {
        uint8_t samples = 120;
        uint32_t humi = 0;
        uint16_t array[120];
        for (int i = 0; i < samples; i++) {
            array[i] = analogRead(SALT_PIN);
            delay(2);
        }
        std::sort(array, array + samples);
        for (int i = 1; i < samples - 1; i++) {
            humi += array[i];
        }
        humi /= samples - 2;
        val.salt = humi;
    }
    break;

    case DS18B20_SENSOR_ID: 
    {
        val.temperature = getDsTemperature();
        if (isnan(val.temperature) || val.temperature > 125.0) {
            val.temperature = 0;
        }
    }
    break;

    case VOLTAGE_SENSOR_ID: 
    {
        int vref = 1100;
        uint16_t volt = analogRead(BAT_ADC);
        val.voltage = ((float)volt / 4095.0) * 6.6 * (vref);
    }
    break;
    default:
        break;
    }
    return true;
}

bool ds18b20Begin()
{
    uint8_t i;

    ds.begin(DS18B20_PIN);
    if (!ds.search(ds18b20Addr)) {
        ds.reset_search();
        return false;
    }

    Serial.print("ROM =");
    for ( i = 0; i < 8; i++) {
        Serial.write(' ');
        Serial.print(ds18b20Addr[i], HEX);
    }

    if (OneWire::crc8(ds18b20Addr, 7) != ds18b20Addr[7]) {
        Serial.println("CRC is not valid!");
        return false;
    }
    Serial.println();
    // the first ROM byte indicates which chip
    switch (ds18b20Addr[0]) {
    case 0x10:
        Serial.println("  Chip = DS18S20");  // or old DS1820
        ds18b20Type = 1;
        break;
    case 0x28:
        Serial.println("  Chip = DS18B20");
        ds18b20Type = 0;
        break;
    case 0x22:
        Serial.println("  Chip = DS1822");
        ds18b20Type = 0;
        break;
    default:
        Serial.println("Device is not a DS18x20 family device.");
        return false;
    }

    return true;
}

bool dhtSensorProbe()
{
    dht.begin();
    delay(2000);// Wait a few seconds between measurements.
    int i = 5;
    while (i--) {
        // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
        float h = dht.readHumidity();
        // Check if any reads failed and exit early (to try again).
        if (isnan(h)) {
            Serial.println("Failed to read from DHT sensor!");
        } else {
            return true;
        }
        delay(500);
    }
    return false;
}

void deviceProbe(TwoWire &t)
{
    uint8_t err, addr;
    int nDevices = 0;

    for (addr = 1; addr < 127; addr++) {
        t.beginTransmission(addr);
        err = t.endTransmission();
        if (err == 0) {

            switch (addr) {
            case OB_BH1750_ADDRESS:
                has_lightSensor = true;
                Serial.println("BH1750 light sensor found!");
                break;
            case OB_BME280_ADDRESS:
                has_bmeSensor = true;
                Serial.println("BME280 temperature and humidity sensor found!");
                break;
            case OB_SHT3X_ADDRESS:
                has_sht3xSensor = true;
                Serial.println("SHT3X temperature and humidity sensor found!");
                break;
            default:
                Serial.print("I2C device found at address 0x");
                if (addr < 16)
                    Serial.print("0");
                Serial.print(addr, HEX);
                Serial.println(" !");
                break;
            }
            nDevices++;
        } else if (err == 4) {
            Serial.print("Unknow error at address 0x");
            if (addr < 16)
                Serial.print("0");
            Serial.println(addr, HEX);
        }
    }
}
/*
  *发送数据到TCP服务器
 */
void sendtoTCPServer(String p)
{
  if (!TCPclient.connected()) 
  {
    Serial.println("Client is not readly");
    return;
  }
  TCPclient.print(p);
  Serial.println("[Send to TCPServer]:String");
}


/*
  *初始化和服务器建立连接
*/
void startTCPClient()
{
  if(TCPclient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT)))
  {
    Serial.print("\nConnected to server:");
    Serial.printf("%s:%d\r\n",TCP_SERVER_ADDR,atoi(TCP_SERVER_PORT));
    preTCPConnected = true;
    preHeartTick = millis();
    TCPclient.setNoDelay(true);
  }
  else
  {
    Serial.print("Failed connected to server:");
    Serial.println(TCP_SERVER_ADDR);
    TCPclient.stop();
    preTCPConnected = false;
  }
  preTCPStartTick = millis();
}

void doWiFiTick()
{
  static bool taskStarted = false;
  static uint32_t lastWiFiCheckTick = 0;

  //未连接1s重连
  if ( WiFi.status() != WL_CONNECTED ) {
    if (millis() - lastWiFiCheckTick > 1000) {
      lastWiFiCheckTick = millis();
    }
  }
  //连接成功建立
  else {
    if (taskStarted == false) {
      taskStarted = true;
      Serial.println("wifi is connected.");
      Serial.print("ssid:");
      Serial.println(WiFi.SSID());
      Serial.print("psw:");
      Serial.println(WiFi.psk());
      startTCPClient();
    }
  }
}
/*
  *检查数据，发送数据
*/
void doTCPClientTick(){
 //检查是否断开，断开后重连
   if(WiFi.status() != WL_CONNECTED) 
   {
      Serial.println("The WiFi is not conected.");
      return;
   }

  if (!TCPclient.connected()) {//断开重连

  if(preTCPConnected == true){

    preTCPConnected = false;
    preTCPStartTick = millis();
    Serial.println();
    Serial.println("TCP Client disconnected.");
    TCPclient.stop();
  }
  else if(millis() - preTCPStartTick > 1*1000)//重新连接
    startTCPClient();
  }
  else
  {
    if (TCPclient.available()) {//收数据
      char c =TCPclient.read();
      TcpClient_Buff +=c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();
      
      if(TcpClient_BuffIndex>=MAX_PACKETSIZE - 1){
        TcpClient_BuffIndex = MAX_PACKETSIZE-2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
      preHeartTick = millis();
    }
    if(millis() - preHeartTick >= upDataTime){//上传数据
      preHeartTick = millis();

      /*********************数据上传*******************/
      String upstr = "";
      upstr = "cmd=2&uid="+UID+"&topic="+TOPIC+"&msg=#"+data1+"#"+data2+"#"+data3+"#"+data4+"#"+data5+"\r\n";
      sendtoTCPServer(upstr);
      upstr = "";
    }
  }
  if((TcpClient_Buff.length() >= 1) && (millis() - TcpClient_preTick>=200))
  {//data ready
    TCPclient.flush();
    Serial.print("Buff:");
    Serial.println(TcpClient_Buff);

    TcpClient_Buff="";
    TcpClient_BuffIndex = 0;
  }
}

void setup()
{
  Serial.begin(115200);
  button.setLongClickHandler(restoreFactory);
  useButton.setLongClickHandler(sleepHandler);
  loadConfig();
  waitKey();
  initWiFi();

  pinMode(POWER_CTRL, OUTPUT);
  digitalWrite(POWER_CTRL, HIGH);
  delay(100);

  Wire.begin(I2C_SDA, I2C_SCL);
  Wire1.begin(I2C1_SDA, I2C1_SCL);

  Serial.println("-------------Devices probe-------------");
  deviceProbe(Wire);
  deviceProbe(Wire1);

  //Check DHT11 temperature and humidity sensor
  if (!dhtSensorProbe()) 
  {
      has_dht11 = false;
      Serial.println("Warning: Failed to find DHT11 temperature and humidity sensor!");
  } 
  else 
  {
      has_dht11 = true;
      Serial.println("DHT11 temperature and humidity sensor init succeeded.");
  }

  //Check SHT3x temperature and humidity sensor
  if (has_sht3xSensor) 
  {
      if (!sht31.begin(OB_SHT3X_ADDRESS)) 
      {   // Set to 0x45 for alternate i2c addr
          Serial.println("Warning: Failed to find SHT3x temperature and humidity sensor!");
      } 
      else 
      {
          has_sht3xSensor = false;
          Serial.println("SHT3X temperature and humidity sensor init succeeded, using SHT3X");
      }
  } else 
  {
      // Destroy Wire1 if SHT3x sensor does not exist
      Wire1.end();
      // If initialization fails, look for a One-Wire sensor present
      if (ds18b20Begin()) 
      {
          has_ds18b20 = true;
          Serial.println("DS18B20 temperature sensor init succeeded, using DS18B20");
      } 
      else 
      {
          has_ds18b20 = false;
          Serial.println("Warning: Failed to find DS18B20 temperature sensor!");
      }
    }

    //Check DHT11 or DHT20 temperature and humidity sensor
    if (has_lightSensor) 
    {
        if (!lightMeter.begin()) 
        {
            has_lightSensor = false;
            Serial.println("Warning: Failed to find BH1750 light sensor!");
        } 
        else 
        {
            Serial.println("BH1750 light sensor init succeeded, using BH1750");
        }
    }
}

void loop()
{
  button.loop();
  useButton.loop();

  if (millis() - timestamp > 500) 
  {
    timestamp = millis();

    higrow_sensors_event_t val = {0};
    get_higrow_sensors_event(SOIL_SENSOR_ID, val);
    data3 = val.soli;
    get_higrow_sensors_event(SALT_SENSOR_ID, val);
    data5 = val.salt;
    get_higrow_sensors_event(VOLTAGE_SENSOR_ID, val);
    if (has_dht11) 
    {
        get_higrow_sensors_event(DHTxx_SENSOR_ID, val);
        data1 = val.temperature;
        data2 = val.humidity;
    }
    if (has_lightSensor) 
    {
        get_higrow_sensors_event(BHT1750_SENSOR_ID, val);
        data4 = val.light;
    }
    if (has_bmeSensor) 
    {
        get_higrow_sensors_event(BME280_SENSOR_ID, val);
    }
    if (has_sht3xSensor) 
    {
        get_higrow_sensors_event(SHT3x_SENSOR_ID, val);
    }
    if (has_ds18b20) 
    {
        get_higrow_sensors_event(DS18B20_SENSOR_ID, val);
    }
    doWiFiTick();
    doTCPClientTick();
  }
}
